﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;
using DiscoveryLogic.Analytics.DataObjects;
using DiscoveryLogic.Analytics.DataObjects.Filters;
using DiscoveryLogic.Analytics.DataObjects.Visuals;
using DiscoveryLogic.Analytics.Diagrams.Physics.Renderers;

namespace DiscoveryLogic.Analytics.Diagrams.RadialGraphs
{
    public class RadialGraphCanvas : Canvas
    {
        #region events
        public delegate void OnVerticeSelected(Vertice v);
        public event OnVerticeSelected VerticeSelected;
        #endregion

        #region fields
        private readonly DispatcherTimer _WorldTimer = new DispatcherTimer();
        private Point _OwnerWindowPosition;
        private Point _PrevAbsoluteMousePosition;
        // private Vertice _SelectedVertice = null;
        private int _ZIndex = 0;
        private int _Iteration = 0;
        #endregion

        #region props
        private Vertice _Anchor;
        /// <summary></summary>
        public Vertice Anchor
        {
            get
            {
                return this._Anchor;
            }
            set
            {
                this._Anchor = value;
            }
        }

        private string _NetworkName;
        /// <summary></summary>
        public string NetworkName
        {
            get
            {
                return this._NetworkName;
            }
            set
            {
                this._NetworkName = value;
            }
        }

        private NetworkVisual _NetVisual = NetworkVisual.Default;
        /// <summary></summary>
        public NetworkVisual NetVisual
        {
            get
            {
                return this._NetVisual;
            }
            set
            {
                this._NetVisual = value;
            }
        }

        private NetworkExclusionSetting _ExSetting = NetworkExclusionSetting.Default;
        /// <summary></summary>
        public NetworkExclusionSetting ExSetting
        {
            get
            {
                return this._ExSetting;
            }
            set
            {
                this._ExSetting = value;
            }
        }

        private NetworkInclusionSetting _InSetting = NetworkInclusionSetting.Default;
        /// <summary></summary>
        public NetworkInclusionSetting InSetting
        {
            get
            {
                return this._InSetting;
            }
            set
            {
                this._InSetting = value;
            }
        }

        private List<Vertice> _Vertices;
        /// <summary></summary>
        public List<Vertice> Vertices
        {
            get
            {
                return this._Vertices;
            }
            set
            {
                this._Vertices = value;
            }
        }

        private List<Edge> _Edges;
        /// <summary></summary>
        public List<Edge> Edges
        {
            get
            {
                return this._Edges;
            }
            set
            {
                this._Edges = value;
            }
        }

        private double _AttractForce = 2.0;
        /// <summary></summary>
        public double AttractForce
        {
            get
            {
                return this._AttractForce;
            }
            set
            {
                this._AttractForce = value;
            }
        }

        private double _RepelForce = 20.0;
        /// <summary></summary>
        public double RepelForce
        {
            get
            {
                return this._RepelForce;
            }
            set
            {
                this._RepelForce = value;
            }
        }

        private IGraphRenderer _Renderer;
        /// <summary></summary>
        public IGraphRenderer Renderer
        {
            get
            {
                return this._Renderer;
            }
            set
            {
                this._Renderer = value;
            }
        }

        private IGraphMeasurer _Measurer;
        /// <summary></summary>
        public IGraphMeasurer Measurer
        {
            get
            {
                return this._Measurer;
            }
            set
            {
                this._Measurer = value;
            }
        }

        private Window _OwnerWindow;
        /// <summary></summary>
        public Window OwnerWindow
        {
            get
            {
                return this._OwnerWindow;
            }
            set
            {
                if (value != null)
                {
                    this._OwnerWindow = value;
                    this._OwnerWindowPosition = new Point(0, 0);
                    this._OwnerWindow.LocationChanged += new EventHandler(_OwnerWindow_LocationChanged);
                }
            }
        }

        void _OwnerWindow_LocationChanged(object sender, EventArgs e)
        {
            Vector deltaMovement = new Vector(this._OwnerWindowPosition.X - this._OwnerWindow.Left,
                this._OwnerWindowPosition.Y - this._OwnerWindow.Top);
            this._OwnerWindowPosition = new Point(this._OwnerWindow.Left, this._OwnerWindow.Top);
            foreach (Vertice v in this.Vertices)
            {
                v.Position += deltaMovement;
            }
        }
        #endregion

        #region ctor
        public RadialGraphCanvas()
		{
			this._Vertices=new List<Vertice>();
			this._Edges=new List<Edge>();
			this._WorldTimer.Interval = new TimeSpan(50);
			this._WorldTimer.Tick += new EventHandler(_WorldTimer_Tick);
		}

        public RadialGraphCanvas(List<Vertice> vertices, List<Edge> edges)
            : this()
        {
            this._Vertices = vertices;
            this._Edges = edges;
        }

        public RadialGraphCanvas(
            Dictionary<Guid, Vertice> vertices, Dictionary<Guid, Edge> edges)
            : this()
        {
            this._Vertices = new List<Vertice>();
            if(vertices !=null && vertices.Count>0)
                this._Vertices.AddRange(vertices.Values);
            this._Edges = new List<Edge>();
            if(edges !=null && edges.Count>0)
                this._Edges.AddRange(edges.Values);
        }
		#endregion

        #region layout
        protected override Size ArrangeOverride(Size arrangeSize)
        {
            foreach (UIElement element in this.InternalChildren)
            {
                double left = Canvas.GetLeft(element);
                double top = Canvas.GetTop(element);
                double x = double.IsNaN(left) ? 0 : left;
                double y = double.IsNaN(top) ? 0 : top;
                element.Arrange(new Rect(new Point(x, y), element.DesiredSize));
            }
            return arrangeSize;
        }

        protected override Size MeasureOverride(Size constraint)
        {
            Size size = new Size(double.PositiveInfinity, double.PositiveInfinity);
            foreach (UIElement element in this.InternalChildren)
            {
                element.Measure(size);
            }
            return new Size();
        }

        protected override void OnRender(System.Windows.Media.DrawingContext dc)
        {
            base.OnRender(dc);
            if (this.Renderer != null)
                this.Renderer.RenderEdges(dc, this._NetVisual, this.Vertices, this.Edges);
        }
        #endregion

        #region simulation
        public void StartSimulation()
        {
            if (!this._WorldTimer.IsEnabled)
            {
                this._Iteration = 0;
                this._Renderer = new RadialRenderer();
                this._Renderer.GraphRenderFinished += new OnFinishedGraphRendering(_Renderer_GraphRenderFinished);
                this._WorldTimer.IsEnabled = true;
            }
        }

        void _Renderer_GraphRenderFinished()
        {
            this.StopSimulation();
        }

        public void StopSimulation()
        {
            this._WorldTimer.IsEnabled = false;
            this._Iteration = 0;
        }

        void _WorldTimer_Tick(object sender, EventArgs e)
        {
            Rect constraintRect = new Rect(0, 0, this.ActualWidth, this.ActualHeight);
            lock (this.Vertices)
            {
                this._Iteration++;
                if (this._Renderer != null)
                {
                    if (this._Renderer.IsStopped)
                    {
                        this.StopSimulation();
                    }
                    else
                    {
                        this._Renderer.UpdatePositions(
                            this.Vertices, this.Edges,
                            constraintRect, this._Iteration,
                            this._AttractForce, this._RepelForce);
                    }
                }
                foreach (Vertice v in this.Vertices)
                {
                    v.SetDisabled(this.IsDisabled(v, this.InSetting, this.ExSetting), this.NetVisual.EntityVisuals[v.Tag.EntityType]);
                    v.SnapControl();
                }
            }
            this.InvalidateVisual();
        }
        #endregion

        #region drag-drop
        public void Canvas_MouseUp(object sender, MouseButtonEventArgs e)
        {
            if (e.ButtonState == MouseButtonState.Released)
            {
                foreach (Vertice v in this._Vertices)
                    v.CanMove = true;
            }
        }

        public void Canvas_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                Point absolutePosition = Mouse.GetPosition(this);
                Rect constraintRect = new Rect(0, 0, this.ActualWidth, this.ActualHeight);
                foreach (Vertice v in this._Vertices)
                {
                    if (v.Selected)
                    {
                        v.SetPosition(
                            new Vector(
                                v.Position.X +
                                (absolutePosition.X - this._PrevAbsoluteMousePosition.X),
                                v.Position.Y +
                                (absolutePosition.Y - this._PrevAbsoluteMousePosition.Y)),
                            constraintRect);
                        v.SnapControl();
                    }
                }
                this._PrevAbsoluteMousePosition = absolutePosition;
                this.InvalidateVisual();
            }
        }

        public void Canvas_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                this._PrevAbsoluteMousePosition = Mouse.GetPosition(this);
                var verticeSelected = from particle in this.Vertices
                                      where particle.Control == sender
                                      select particle;
                if (verticeSelected.Count() > 0)
                {
                    Vertice v = verticeSelected.First();
                    foreach (Vertice v2 in this.Vertices)
                    {
                        if (v2 != v)
                        {
                            v2.SetSelected(false, this._NetVisual.EntityVisuals[v2.Tag.EntityType]);
                            v2.CanMove = true;
                        }
                    }
                    v.CanMove = false;
                    v.Control.SetValue(Canvas.ZIndexProperty, this._ZIndex++);
                    v.SetSelected(true, this._NetVisual.EntityVisuals[v.Tag.EntityType]);
                    foreach(Vertice v2 in this._Vertices)
                    {
                        if(v2.ID != v.ID)
                            v2.SetSelected(false, this._NetVisual.EntityVisuals[v2.Tag.EntityType]);
                    }
                    if (this.VerticeSelected != null)
                        this.VerticeSelected(v);
                    return;
                }
                else
                {
                    foreach (Vertice v in this.Vertices)
                        v.SetSelected(false, this._NetVisual.EntityVisuals[v.Tag.EntityType]);
                }
            }
        }

        public void Canvas_MouseEnter(object sender, MouseEventArgs e)
        {
            (sender as Control).SetValue(Canvas.ZIndexProperty, this._ZIndex++);
        }
        #endregion

        #region selection
        public void SelectEntity(EntityBase entity)
        {
            foreach (Vertice v2 in this.Vertices)
            {
                if (v2.Tag.PKValue() == entity.PKValue())
                {
                    v2.SetSelected(true, this._NetVisual.EntityVisuals[v2.Tag.EntityType]);
                }
                else
                {
                    v2.SetSelected(false, this._NetVisual.EntityVisuals[v2.Tag.EntityType]);
                }
            }
            this.InvalidateVisual();
        }
        #endregion

        #region filter
        private bool IsDisabled(Vertice v, NetworkInclusionSetting inSetting, NetworkExclusionSetting exSetting)
        {
            if (!inSetting.IsEnabled && !exSetting.IsEnabled)
                return false;
            if (inSetting.IsEnabled)
            {
                if (inSetting.IncludeEntityTypes.Contains(v.Tag.EntityType))
                    return false;
            }
            if (exSetting.IsEnabled)
            {
                if (exSetting.ExcludeEntityTypes.Contains(v.Tag.EntityType))
                    return true;
            }
            return false;
        }
        #endregion
    }
}
